feat: link a plugin's documentation from the plugin editor - #3462
Merged
Conversation
guoqqqi
approved these changes
Aug 4, 2026
LiteSun
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please answer these questions before submitting a pull request, or your PR will get closed.
Why submit this pull request?
What changes will this PR take into?
Choosing a plugin meant picking from a grid of bare names —
ai-aws-content-moderation,graphql-proxy-cache,workflow,api-breaker— with no description and nothing to click. Once a plugin was open, the drawer showed its name and the editor was handed that plugin's JSON schema, so there was validation and completion, but nothing anywhere said what the plugin was for, and nothing linked out. A user who did not already know the plugin had to leave the dashboard and search for it.Each plugin now carries a link to its documentation: an icon on the plugin card, and an icon with a label in the editor drawer's title row.
Why a link rather than a description
The gateway cannot supply the text. Of the plugins returned by
GET /apisix/admin/plugins?all=trueon APISIX 3.17.0, none carries aschema.description. Bundling a name-to-description table in the dashboard would mean 107 entries times five locales, maintained against a plugin list that changes every gateway release — and a stale description is worse than none. The docs site is the only available source, and its URL is derivable from the plugin name.The URL is derived, and the exceptions are enumerated
src/utils/pluginDocs.tsmaps a plugin name and the UI language to a URL or tonull. Coverage was measured against the live docs site across every plugin name the gateway reports in both subsystems (106 http plusmqtt-proxyfrom stream):serverless-pre-function,serverless-post-functionshare/plugins/serverless/)example-pluginis the gateway's sample;aihas no page of its own)ai-cache,ai-lakera-guard,mcp-bridge— they resolve only under/next/)The two
serverless-*names get a slug override. The other five render no link at all: a dashboard that is the first thing many people see of APISIX should not hand out broken links, and a missing affordance is a smaller failure than one that lies.The three unreleased plugins deliberately do not link
/next/. That would resolve today, but it points users at documentation for a version they are not running, and those entries would expire silently once the released docs catch up. Their absence is self-correcting; a stale/next/pointer is not.Following the UI language
The link resolves to
/zh/docs/...when the dashboard is in Chinese and to the English docs otherwise. Coverage was measured separately for Chinese and is identical — 100 of 107, and the same seven names fail in both locales, character for character — so following the language introduces no failure mode that English does not already have. The docs site publishes English and Chinese only;/es/,/de/and/tr/are 404, so those three fall back to English rather than producing a URL that does not exist.The comparison is exact (
language === 'zh'), not a prefix match:src/config/i18n.tsderivessupportedLngsfrom the resource keys, so the only possible values are the five bare codes with no regional variants. This matches howantdConfigProvideralready indexes its locale table.Why the control is an anchor
PluginDocsLinkrenders a MantineAnchor, i.e. a real<a href target="_blank" rel="noopener noreferrer">, following the patternHeader/index.tsxalready uses for the docs button. The repo'sRouteLinkBtnis a MantineButtoncarryinghref; middle-click, ⌘-click and "copy link address" are exactly what someone reaching for documentation does, and none of them work on a<button>.RouteLinkBtn's own call sites are deliberately untouched.The e2e assertions use
getByRole('link', …), which fails if the control ever regresses to a button.Two call sites, six surfaces
PluginCardandPluginEditorDrawerare both shared: the card renders in the select-plugins picker, in the list of already-selected plugins, and on the Plugin Metadata page; the drawer is used by the plugins form field and by Plugin Metadata. Two edits therefore cover every place a plugin is chosen or configured.The link is unconditional on drawer mode — it shows in
add,editandviewalike. What a plugin does is the same question whether you are choosing it, configuring it, or reading someone else's configuration, andviewis what a read-only operator sees.On the card the link is icon-only, with an
aria-labelnaming the plugin, so a screen reader can tell which of a hundred identical icons it has landed on. In the drawer the label is visible and noaria-labelis set: overriding a visible label with text that does not contain it would break WCAG 2.5.3 Label in Name. The drawer's label reuses the existing top-leveldocskey rather than introducing a second key meaning the same thing.One new string,
form.plugins.docsFor, in all five locales. It is interpolated withescapeValue: false, prophylactically — #3459 shipped an accessible name readingR&Dbefore that was caught.Related issues
Closes #3461
Checklist:
Tests:
src/utils/pluginDocs.test.ts— 13 cases: a plain name; bothserverless-*collapsing toserverless; each of the five undocumented names returningnull; the/zhprefix;es/de/trfalling back to English rather than emitting a/es/URL that does not exist; and the zh prefix combined with a slug override, which is the interaction the individual cases miss.e2e/tests/regression/plugin.docs-link.spec.ts— five tests: the drawer link carries the exact expectedhrefandtarget="_blank"; a plugin with no documentation page (example-plugin) exposes no link, which is the assertion that fails if the exception table is dropped; the same link'shrefgains/zh/after switching the UI to Chinese; a picker card exposes a link whose accessible name iskey-auth documentation, which is the only coverage of the component's icon-only branch; and an undocumented plugin's card exposes no link.The card sits inside a
Combobox.Option, so whether a nested anchor's click survives Mantine's option handling was checked by hand in a real browser rather than inferred: clicking the icon opens a new tab on the plugin's documentation page and leaves the picker open and unchanged. NostopPropagationwas needed, so none was added.Verified:
pnpm test(122 passed),pnpm lint,pnpm exec tsc -b, and both a--mode testand a productionpnpm build, all clean. The new spec passes 5/5 in isolation.A full Playwright run on this machine is not currently a usable signal, and the reason is worth stating rather than hiding behind a pass rate. The suite's own
bulk-1000spec drives etcd into multi-second stalls — its logs show 51-second raft reads on the health-probe key — after which unrelated specs fail withhas no healthy etcd endpoint available. Separately, this machine cannot run the Monaco-backed specs headless at all. Both were confirmed to be independent of this change by checking outmaster(89fc2ed1), rebuilding, and re-running the failures: every one of the eight specs sampled that way fails identically onmaster, and the specs that recovered once etcd was healthy — including this PR's own — recovered on both trees.